package com.ruoyi.framework.minio;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ThrowableUtil;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import javax.annotation.PostConstruct;
import java.io.*;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

//http://events.jianshu.io/p/6de1acde167d
//minio 分片上传 https://blog.csdn.net/qq_42852943/article/details/127734905
@Component
public class MinioService {
    private final Logger logger = LoggerFactory.getLogger(MinioService.class);
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinioClient minioClientOuter;
    @Autowired
    private MinIoClientConfig minIoClientConfig;

    /**
     * 初始化默认存储桶
     */
    @PostConstruct
    public void initDefaultBucket() throws Exception{
//        String defaultBucketName = minIoClientConfig.getBucketName();
//        if (existBucket(defaultBucketName)) {
//            logger.info("默认存储桶：defaultBucketName已存在");
//        } else {
//            logger.info("创建默认存储桶：defaultBucketName");
//            makeBucket(minIoClientConfig.getBucketName());
//        }
    }

    /**
     * description: 判断bucket是否存在，不存在则创建
     * @return: void
     */
    public void existBucketAndMake(String name) throws Exception{
        boolean exists = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(name).build());
        if (!exists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
        }
    }
    public boolean existBucket(String bucketName) throws Exception{
         boolean exists = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(bucketName).build());
        return exists;
    }

    /**
     * 创建存储bucket
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) throws Exception{
        if(!existBucket(bucketName)){
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        }
        return true;
    }

    /**
     * 查询所有存储桶
     *
     * @return Bucket 集合
     */
    public List<Bucket> listBuckets() throws Exception{
        return minioClient.listBuckets();
    }

    /**
     * 删除存储bucket
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
//    public Boolean removeBucket(String bucketName) throws Exception{
//        removeBucket(bucketName, false);
//        minioClient.removeBucket(RemoveBucketArgs.builder()
//                .bucket(bucketName)
//                .build());
//        return true;
//    }
    /**
     * 删除一个桶 根据桶是否存在数据进行不同的删除
     * 桶为空时直接删除
     * 桶不为空时先删除桶中的数据，然后再删除桶
     *
     * @param bucketName 桶名
     */
    public void removeBucket(String bucketName, boolean bucketNotNull) throws Exception {
        if (bucketNotNull) {
            deleteBucketAllObject(bucketName);
        }
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }
    /**
     * description: 上传文件
     * @param multipartFile
     * @return: java.lang.String

     */
    public List<MinioFile> upload(String bucketName, MultipartFile[] multipartFile) throws Exception{
        List<MinioFile> files = new ArrayList<>(multipartFile.length);
        existBucketAndMake(bucketName);
        for (MultipartFile file : multipartFile) {
            MinioFile minioFile = new MinioFile();
            String fileName = file.getOriginalFilename();
            String[] split = fileName.split("\\.");
            String ext = split[1].toLowerCase();
            String uuid = IdUtils.fastUUID();
            String objectName = uuid+"."+ext;
            InputStream in = null;
            try {
                in = file.getInputStream();
                ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(in, in.available(), -1)
                        .contentType(file.getContentType())
                        .build()
                );
                minioFile.setBucket(bucketName);
                minioFile.setFileName(fileName);
                minioFile.setFileSize(file.getSize());
                minioFile.setFileExt(ext);
                minioFile.setObjectName(objectName);
                //path_url = 'https://' + self.endpoint + '/' + result.bucket_name + '/' + result.object_name
                String url = minIoClientConfig.getFileUrl(bucketName,objectName);
                minioFile.setUrl(url);
                files.add(minioFile);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return files;
    }

    public MinioFile upload(String bucketName, MultipartFile file) {
        MinioFile minioFile = null;
        InputStream in = null;
        try {
            existBucketAndMake(bucketName);
            minioFile = new MinioFile();
            String fileName = file.getOriginalFilename();
            String[] split = fileName.split("\\.");
            String ext = split[1].toLowerCase();
            String uuid = IdUtils.fastUUID();
            String objectName = uuid+"."+ext;
            in = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(in, in.available(), -1)
                    .contentType(file.getContentType())
                    .build());
            minioFile.setBucket(bucketName);
            minioFile.setFileName(fileName);
            minioFile.setFileSize(file.getSize());
            minioFile.setFileExt(ext);
            minioFile.setObjectName(objectName);
            //path_url = 'https://' + self.endpoint + '/' + result.bucket_name + '/' + result.object_name
            String url = minIoClientConfig.getFileUrl(bucketName,objectName);
            minioFile.setUrl(url);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return minioFile;
    }


    public MinioFile upload(String bucketName, File file) {
        MinioFile minioFile = null;
        InputStream in = null;
        try {
            existBucketAndMake(bucketName);
            minioFile = new MinioFile();
            String fileName = file.getName();
            String ext = FileUtils.getExt(file.getName());
            String uuid = IdUtils.fastUUID();
            String objectName = StringUtils.isEmpty(ext)?uuid:uuid+"."+ext;
            in = new FileInputStream(file);
            ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(in, in.available(), -1)
                    .build()
            );
            minioFile.setBucket(bucketName);
            minioFile.setFileName(fileName);
            minioFile.setFileSize(file.length());
            minioFile.setFileExt(ext);
            minioFile.setObjectName(objectName);
            //path_url = 'https://' + self.endpoint + '/' + result.bucket_name + '/' + result.object_name
            String url = minIoClientConfig.getFileUrl(bucketName,objectName);
            minioFile.setUrl(url);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return minioFile;
    }

    public MinioFile uploadFile(InputStream inputStream, String bucket, String objectName) {
        try {
            existBucketAndMake(bucket);
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .stream(inputStream, inputStream.available(), -1)
                    .build());
            return new MinioFile(bucket,objectName);
        }catch (Exception e) {
            logger.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
                }
            }
        }
        return null;
    }

    /**
     * GetObject接口用于获取某个文件（Object）。此操作需要对此Object具有读权限。
     *
     * @param bucketName 桶名
     * @param objectName 文件路径
     */
    public InputStream getObject(String bucketName, String objectName) throws Exception{
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }
    /**
     * description: 下载文件
     * @param objectName
     * @return: org.springframework.http.ResponseEntity<byte [ ]>
     */
    public ResponseEntity<byte[]> download(String bucketName,String objectName) {
        ResponseEntity<byte[]> responseEntity = null;
        logger.info("download step1........");
        InputStream in = null;
        logger.info("download step2........");
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName).object(objectName).build());
            out = new ByteArrayOutputStream();
            logger.info("download step3........");
            IOUtils.copy(in, out);
            logger.info("download step4........");
            //封装返回值
            byte[] bytes = out.toByteArray();
            logger.info("download step5........");
            HttpHeaders headers = new HttpHeaders();
            logger.info("download step6........");
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            logger.info("download step7........");
            headers.setContentLength(bytes.length);
            logger.info("download step8........");
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            logger.info("download step9........");
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
            logger.info("download step10........");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        logger.info("download step11........");
        logger.info("download step11........{}",responseEntity);
        return responseEntity;
    }

    public ResponseEntity<StreamingResponseBody> download1(String bucketName, String objectName) {
        // Initialize StreamingResponseBody to allow streaming the file content
        logger.info("download step1........");
        StreamingResponseBody streamingResponseBody = outputStream -> {
            InputStream in = null;
            logger.info("download step2........");
            try {
                // Get the file from Minio as InputStream
                in = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
                logger.info("download step3........");
                // Stream the file content to the response output stream
                IOUtils.copy(in, outputStream); // Streams the content from MinIO to the response
                logger.info("download step4........");
            } catch (Exception e) {
                e.printStackTrace();
                logger.info("download step5........");
            } finally {
                try {
                    if (in != null) {
                        in.close(); // Close InputStream after reading
                        logger.info("download step6........");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.info("download step8........");
                }
            }
        };

        // Set the response headers (Content-Disposition, Content-Type, etc.)
        HttpHeaders headers = new HttpHeaders();
        logger.info("download step9.......");
        try {
            headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectName, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        // Set headers for download
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setAccessControlExposeHeaders(Arrays.asList("*"));
        logger.info("download step10........");
        // Return ResponseEntity with StreamingResponseBody
        return new ResponseEntity<>(streamingResponseBody, headers, HttpStatus.OK);
    }

    public void download(String bucketName,String objectName,OutputStream outputStream) {
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName).object(objectName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 查看文件对象
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<MinioFile> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<MinioFile> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                MinioFile objectItem = new MinioFile();
                objectItem.setObjectName(item.objectName());
                objectItem.setFileSize(item.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

    /**
     * 批量删除文件对象
     * @param bucketName 存储bucket名称
     * @param objects 对象名称集合
     */
    public Iterable<Result<DeleteError>> removeObjects(String bucketName,
                                                       List<String> objects) {
        List<DeleteObject> dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
        return results;
    }
    /**
     * 删除桶中所有的对象
     *
     * @param bucketName 桶对象
     */
    public void deleteBucketAllObject(String bucketName) throws Exception {
        List<String> list = listObjectNames(bucketName);
        if (!list.isEmpty()) {
            for (String objectName : list) {
                deleteObject(bucketName, objectName);
            }
        }
    }
    public void deleteObject(String bucketName, String objectName) throws Exception{
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 上传分片文件
     *
     * @param inputStream 流
     * @param objectName  存入桶中的对象名
     * @param bucketName  桶名
     * @return ObjectWriteResponse
     */
    public MinioFile putChunkObject(InputStream inputStream, String bucketName, String objectName) throws Exception {
        try {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
            return new MinioFile(objectName, bucketName);
        }catch (Exception e) {
            throw new Exception(e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 返回临时带签名、Get请求方式的访问URL
     *
     * @param bucketName 桶名
     * @param objectName   Oss文件路径
     * @return 临时带签名、Get请求方式的访问URL
     */
    public String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {
        return minioClientOuter.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
    }

    public String getBucketObjectUrl(String bucket, String object) {
        try {
            GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucket)
                    .object(object)
                    .expiry(60 * 60 * 24)   //生成的预签名url可访问的有效时间，最大期限7天
                    .build();
            return minioClientOuter.getPresignedObjectUrl(build);
        } catch (Exception e) {
            logger.error("minioUrl",e);
            return null;
        }
    }

    /**
     * 返回临时带签名、过期时间为1天的PUT请求方式的访问URL
     *
     * @param bucketName  桶名
     * @param filePath    Oss文件路径
     * @param queryParams 查询参数
     * @return 临时带签名、过期时间为1天的PUT请求方式的访问URL
     */
    public String getPresignedObjectUrl(String bucketName, String filePath, Map<String, String> queryParams) throws Exception{
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(filePath)
                        .expiry(1, TimeUnit.DAYS)
                        .extraQueryParams(queryParams)
                        .build());
    }






    /**
     * 查询桶的对象信息
     *
     * @param bucketName 桶名
     * @param recursive  是否递归查询
     * @return 桶的对象信息
     */
    public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
    }

    /**
     * 获取带签名的临时上传元数据对象，前端可获取后，直接上传到Minio
     *
     * @param bucketName 桶名称
     * @param fileName   文件名
     * @return Map<String, String>
     */
    public Map<String, String> getPresignedPostFormData(String bucketName, String fileName) throws Exception{
        // 为存储桶创建一个上传策略，过期时间为7天
        PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(1));
        // 设置一个参数key，值为上传对象的名称
        policy.addEqualsCondition("key", fileName);
        // 添加Content-Type，例如以"image/"开头，表示只能上传照片，这里吃吃所有
        policy.addStartsWithCondition("Content-Type", MediaType.ALL_VALUE);
        // 设置上传文件的大小 64kiB to 10MiB.
        //policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
        return minioClient.getPresignedPostFormData(policy);
    }


    public String generateFileInMinioName(String originalFilename) {
        return "files" + StrUtil.SLASH + DateUtil.format(new Date(), "yyyy-MM-dd") + StrUtil.SLASH + UUID.randomUUID() + StrUtil.UNDERLINE + originalFilename;
    }




    /**
     * 文件合并，将分块文件组成一个新的文件
     * @param bucketName       合并文件生成文件所在的桶
     * @param fileName         原始文件名
     * @param sourceObjectList 分块文件集合
     * @return OssFile
     */
    public MinioFile composeObject(String bucketName, String fileName, List<ComposeSource> sourceObjectList) throws Exception {
        String ext = FileUtils.getExt(fileName);
        String objectName = UUID.randomUUID() + "." + ext;
        minioClient.composeObject(ComposeObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .sources(sourceObjectList)
                .build());
        String url = getPresignedObjectUrl(bucketName, objectName);
        return new MinioFile(bucketName, objectName,url);
    }


    /**
     * 文件合并，将分块文件组成一个新的文件
     *
     * @param bucketName       合并文件生成文件所在的桶
     * @param objectName       原始文件名
     * @param sourceObjectList 分块文件集合
     * @return OssFile
     */
    public MinioFile composeObject(List<ComposeSource> sourceObjectList, String bucketName, String objectName) throws Exception{
        minioClient.composeObject(ComposeObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .sources(sourceObjectList)
                .build());
        MinioFile minioFile = new MinioFile(bucketName, objectName);
//        String url = getPresignedObjectUrl(bucketName, objectName);
//        minioFile.setUrl(url);
        return minioFile;
    }

    /**
     * 文件合并，将分块文件组成一个新的文件
     *
     * @param originBucketName 分块文件所在的桶
     * @param targetBucketName 合并文件生成文件所在的桶
     * @param objectName       存储于桶中的对象名
     * @return OssFile
     */
    public MinioFile composeObject(String originBucketName, String targetBucketName, String objectName) throws Exception{
        makeBucket(targetBucketName);
        Iterable<Result<Item>> results = listObjects(originBucketName, true);
        List<String> objectNameList = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();
            objectNameList.add(item.objectName());
        }
        if (ObjectUtils.isEmpty(objectNameList)) {
            throw new IllegalArgumentException(originBucketName + "桶中没有文件，请检查");
        }
        List<ComposeSource> composeSourceList = new ArrayList<>(objectNameList.size());
        // 对文件名集合进行升序排序
        objectNameList.sort((o1, o2) -> Integer.parseInt(o2) > Integer.parseInt(o1) ? -1 : 1);
        for (String object : objectNameList) {
            composeSourceList.add(ComposeSource.builder()
                    .bucket(originBucketName)
                    .object(object)
                    .build());
        }
        return composeObject(composeSourceList, targetBucketName, objectName);
    }

    /**
     * 查询桶中所有的对象名
     *
     * @param bucketName 桶名
     * @return objectNames
     */
    public List<String> listObjectNames(String bucketName) throws Exception {
        List<String> objectNameList = new ArrayList<>();
        if (existBucket(bucketName)) {
            Iterable<Result<Item>> results = listObjects(bucketName, true);
            for (Result<Item> result : results) {
                String objectName = result.get().objectName();
                objectNameList.add(objectName);
            }
        }
        return objectNameList;
    }
}
